package cc.android.testapp.xp.hooker.textmodify;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import cc.android.testapp.util.CLog;
import cc.commons.util.extra.CList;
import cc.commons.util.reflect.FieldUtil;
import cc.commons.util.reflect.MethodUtil;

public class ViewPropDialog extends AlertDialog {

    public static final HashSet<Class<?>> mBaseType = new HashSet<>();
    private static boolean mIsShow = false;
    public static WeakReference<Activity> mNowAct = new WeakReference<>(null);
    protected final Context mContext;
    protected final List<View> mCheckView = new ArrayList<>();
    protected ViewAdapter mAdapter;
    protected Stack<Pair<ViewAdapter, Integer>> mStack = new Stack<>();
    protected ListView mListView;
    public Object mCurrent = null;
    protected Stack<Object> mStackCurrent = new Stack<>();
    protected BorderView mBorder;

    static {
        mBaseType.add(Boolean.class);
        mBaseType.add(Byte.class);
        mBaseType.add(Short.class);
        mBaseType.add(Integer.class);
        mBaseType.add(Character.class);
        mBaseType.add(Long.class);
        mBaseType.add(Float.class);
        mBaseType.add(Double.class);
        mBaseType.add(CharSequence.class);
        mBaseType.add(Object.class);
    }

    public ViewPropDialog(Context context, List<View> pView) {
        super(context);
        this.mContext = context;

        if (context instanceof Activity) {
            ignoreEdit(((Activity) context).getWindow().getDecorView());
        }
        this.mCheckView.addAll(pView);
    }

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);

        ignoreEdit(this.getWindow().getDecorView());
        this.mBorder = ignoreEdit(new BorderView(this.mContext));
        this.getWindow().setBackgroundDrawable(new ColorDrawable(Color.argb(150, 0, 0, 0)));
        this.mListView = ignoreEdit(new ListView(this.getContext()));
        this.mListView.setDivider(new ColorDrawable(Color.GRAY));
        this.mListView.setDividerHeight(2);
        this.mListView.setPadding(10, 10, 10, 0);
        this.setContentView(this.mListView);
        if (this.mCheckView.size() == 1) intoValue(this.mCheckView.get(0));
        else intoValue(this.mCheckView);
    }


    public void intoValue(Object pValue) {
        if (this.mCurrent != null)
            this.mStackCurrent.push(this.mCurrent);
        this.mCurrent = pValue;
        this.onCurrentChange();
        if (this.mAdapter != null)
            this.mStack.push(new Pair<>(this.mAdapter, this.mListView.getFirstVisiblePosition()));

        ArrayList<Pair<String, Object>> tValues = new ArrayList<>();
        Class<?> tType = pValue.getClass();
        if (tType.isArray() || Collection.class.isAssignableFrom(tType)) {
            List<Object> tValue;
            if (tType.isArray()) tValue = Arrays.asList((Object[]) pValue);
            else tValue = new ArrayList<>((Collection<?>) pValue);
            if (tValue.isEmpty()) tValues.add(new Pair<>("这里没有数据呦", ""));
            else {
                //tValue.sort(Comparator.comparing(v -> v.getClass().getName()));
                int tIndex = 0;
                for (Object sObj : tValue) {
                    tValues.add(new Pair<>(getKey((++tIndex) + ". ", sObj), sObj));
                }
            }

        } else if (Map.class.isAssignableFrom(tType)) {
            Map<?, ?> tMap = (Map) pValue;
            if (tMap.isEmpty()) tValues.add(new Pair<>("这里没有数据呦", ""));
            else {
                for (Map.Entry<?, ?> sEntry : tMap.entrySet()) {
                    tValues.add(new Pair<>(getKey(String.valueOf(sEntry.getKey()), sEntry.getValue()), sEntry.getValue()));
                }
            }
        } else {
            LinkedHashMap<String, Object> tTo = new LinkedHashMap<>();
            collectFieldAndMethod(pValue, tType, tTo);
            tTo.forEach((key, value) -> tValues.add(new Pair<>(key, value)));
            //Parent
            HashMap<String, LinkedHashMap<String, Object>> tParent = new LinkedHashMap<>();
            int deep = 0;
            do {
                tType = tType.getSuperclass();
                if (tType == null || tType == Object.class) break;

                deep++;
                tTo = new LinkedHashMap<>();
                collectFieldAndMethod(pValue, tType, tTo);
                tParent.put(deep + ". " + tType.getName(), tTo);
            } while (true);
            if (!tParent.isEmpty()) {
                tValues.add(new Pair<>("Parent class: ", tParent));
            }
        }

        this.setAdapter(new ViewAdapter(this.getContext(), this, tValues));
    }

    private void collectFieldAndMethod(Object pValue, Class<?> tType, LinkedHashMap<String, Object> pTo) {
        pTo.put("----====Class Type====----", tType.getName() + "@" + System.identityHashCode(pValue));
        if (pValue instanceof View) {
            View tV = (View) pValue;
            String tIdName = "No id";
            try {
                tIdName = this.getContext().getResources().getResourceEntryName(tV.getId());
            } catch (Resources.NotFoundException ignored) {
            }
            pTo.put("----====View id====----", tIdName + "(" + (tV.getId() == View.NO_ID ? "-1" : Integer.toHexString(tV.getId())) + ")");
            if (MethodUtil.isMethodExist(tType, "getText", false)) {
                Method tMethod = MethodUtil.getMethod(tType, "getText", false);
                pTo.put("----====View text====----", MethodUtil.invokeMethod(tMethod, pValue));
            }
            if (tV.getParent() != null) {
                pTo.put("----====View parent====----", tV.getParent());
            }
            if (tV instanceof ViewGroup) {
                ViewGroup tVG = (ViewGroup) tV;
                ArrayList<View> tSubs = new ArrayList<>();
                for (int i = 0; i < tVG.getChildCount(); i++) {
                    View tSub = tVG.getChildAt(i);
                    if (tSub != null) tSubs.add(tSub);
                }
                if (!tSubs.isEmpty())
                    pTo.put("----====Child Views====----", tSubs);
            }
        }
        //Fields
        HashMap<String, Object> tVs = new LinkedHashMap<>();
        int i = 0;
        Field[] tFields = tType.getDeclaredFields();
        Arrays.sort(tFields, Comparator.comparing(Field::getName));
        for (Field sField : tFields) {
            try {
                tVs.put((++i) + ". " + sField.getName(), FieldUtil.getFieldValue(sField, pValue));
            } catch (Throwable ignored) {
            }
        }
        pTo.put("Field: (" + tVs.size() + ")", tVs);
        //Methods
        tVs = new LinkedHashMap<>();
        i = 0;
        try {
            CList<Method> tMethods = MethodUtil.getDeclaredMethod(tType,
                    m -> m.getParameterTypes().length == 0 && (m.getName().startsWith("get")
                            || m.getName().startsWith("is") || m.getName().startsWith("has")));
            tMethods.sort(Comparator.comparing(Method::getName));
            for (Method sMethod : tMethods) {
                try {
                    tVs.put((++i) + ". " + sMethod.getName() + "()", MethodUtil.invokeMethod(sMethod, pValue));
                } catch (Throwable ignored) {
                }
            }
        } catch (IllegalStateException ignored) {
        }
        pTo.put("Method: (" + tVs.size() + ")", tVs);
    }

    protected void onCurrentChange() {
        if (this.mCurrent instanceof View) this.mBorder.setBoundWithView((View) this.mCurrent);
        else this.mBorder.setBoundWithView(null);
    }

    @Override
    public void onBackPressed() {
        if (!this.mStack.isEmpty()) {
            Pair<ViewAdapter, Integer> tPop = this.mStack.pop();
            this.setAdapter(tPop.first);
            this.mListView.setSelection(tPop.second);
            if (!this.mStackCurrent.isEmpty()) {
                this.mCurrent = this.mStackCurrent.pop();
                this.onCurrentChange();
            }
        } else super.onBackPressed();
    }

    private void setAdapter(ViewAdapter pAdapter) {
        this.mAdapter = pAdapter;
        this.mListView.setAdapter(this.mAdapter);

    }


    @Override
    public synchronized void show() {
        if (isShown()) return;
        mIsShow = true;
        super.show();
        Window window = getWindow();
        if (window != null) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
            window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
        }
    }

    @Override
    public void dismiss() {
        super.dismiss();
        mIsShow = false;
        ViewParent tParent = this.mBorder.getParent();
        if (tParent instanceof ViewGroup) {
            ((ViewGroup) tParent).removeView(this.mBorder);
        }
    }

    public static boolean isShown() {
        return mIsShow;
    }

    /**
     * 当前Object是否为视图且不可见
     *
     * @param pView 视图
     * @return 是否不可见
     */
    public static String getKey(String pKey, Object pView) {
        boolean v = pView instanceof View && !((View) pView).isShown();
        if (v) pKey += "(视图不可见)";
        return pKey;
    }

    private static View mShowView = null;
    private static final ArrayList<View> mShowViews = new ArrayList<>();

    public static void appendShowView(View pView) {
        mShowViews.add(pView);
        if (mShowView == null) {
            mShowView = pView;
            mShowView.postDelayed(() -> {
                new ViewPropDialog(pView.getContext(), mShowViews).show();
                mShowView = null;
                mShowViews.clear();
            }, 100);
        }
    }

    public static <T extends View> T ignoreEdit(T pView) {
        if (pView != null) pView.setTag("IGNORE_HOOK");
        return pView;
    }

    public static class ViewAdapter extends ArrayAdapter<Pair<String, Object>> implements View.OnTouchListener {

        private View mTouchPre = null;
        private long mTouchDownTime = 0L;
        public final ViewPropDialog mDialog;

        public ViewAdapter(@NonNull Context context, ViewPropDialog pDialog, @NonNull List<Pair<String, Object>> objects) {
            super(context, 0, objects);

            this.mDialog = pDialog;
        }

        @NonNull
        @Override
        public View getView(int pPos, @Nullable View pOldView, @NonNull ViewGroup pParent) {
            if (pOldView == null) {
                pOldView = ignoreEdit(LayoutInflater.from(this.getContext()).inflate(
                        android.R.layout.two_line_list_item, pParent, false));
                pOldView.setPadding(0, 0, 0, 15);
            }

            Pair<String, Object> tItem = this.getItem(pPos);
            TextView tTView1 = ignoreEdit(pOldView.findViewById(android.R.id.text1));
            TextView tTView2 = ignoreEdit(pOldView.findViewById(android.R.id.text2));
            List<TextView> tViews = Arrays.asList(tTView1, tTView2);
            for (TextView sView : tViews) {
                sView.setTextIsSelectable(true);
                sView.setOnTouchListener(null);
                sView.setOnClickListener(null);
                sView.setTextColor(Color.WHITE);
            }
            tTView1.setText(tItem.first);
            tTView2.setPadding(10, 0, 0, 0);
            String tStr;
            Object tV = tItem.second;
            if (tV == null) tStr = "null";
            else {
                Class<?> tType = tV.getClass();
                if (tType.isPrimitive() || mBaseType.contains(tType) || CharSequence.class.isAssignableFrom(tType)) {
                    tStr = tV.toString();
                } else if (tType.isArray() && tType.getComponentType().isPrimitive()) {
                    tStr = tType.getName() + MethodUtil.invokeDeclaredMethod(Arrays.class,
                            "toString", tType, null, tV).toString();
                } else {
                    if (Map.class.isAssignableFrom(tType)) {
                        tStr = "Map amount: " + ((Map) tV).size();
                    } else if (List.class.isAssignableFrom(tType)) {
                        tStr = "List amount: " + ((List) tV).size();
                    } else {
                        tStr = tType.getName() + "@" + System.identityHashCode(tV);
                        if (tV instanceof TextView) {
                            tStr += "(" + ((TextView) tV).getText() + ")";
                        }
                    }
                    View.OnClickListener tLis = (v) -> mDialog.intoValue(tV);
                    for (TextView sView : tViews) {
                        sView.setOnTouchListener(this);
                        sView.setOnClickListener(tLis);
                    }
                }
            }
            tTView2.setText(tStr);

            return pOldView;
        }

        @Override
        public boolean onTouch(View pView, MotionEvent pEvent) {
            boolean tResult = false;
            if (pEvent.getAction() == MotionEvent.ACTION_DOWN) {
                this.mTouchPre = pView;
                this.mTouchDownTime = System.currentTimeMillis();
            } else if (pEvent.getAction() == MotionEvent.ACTION_UP) {
                if (this.mTouchPre == pView) {
                    long t = System.currentTimeMillis() - this.mTouchDownTime;
                    if (t < 150) {
                        tResult = pView.callOnClick();
                    }
                }
                this.mTouchPre = null;
            }
            return false;
        }
    }

    public static class BorderView extends View {

        public Rect mBorder = null;
        public final Paint mPaint = new Paint();

        public BorderView(Context context) {
            super(context);

            mPaint.setColor(Color.RED);
            mPaint.setStrokeWidth(10f);
            mPaint.setStyle(Paint.Style.STROKE);

            Activity tAct = null;
            if (context instanceof Activity) {
                tAct = (Activity) context;
            } else {
                tAct = mNowAct.get();
            }
            if (tAct != null) {
                ((ViewGroup) tAct.getWindow().getDecorView()).addView(this);
                CLog.log("边框标记启用成功!");
            } else {
                CLog.log("边框标记启用失败(" + context.getClass().getName() + ")");
            }
        }

        public void setBoundWithView(View pView) {
            if (pView == null) {
                this.mBorder = null;
            } else {
                int[] tLoc = new int[2];
                pView.getLocationOnScreen(tLoc);
                this.mBorder = new Rect(tLoc[0], tLoc[1], tLoc[0] + pView.getWidth(), tLoc[1] + pView.getHeight());
            }
            this.invalidate();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            if (this.mBorder != null) canvas.drawRect(this.mBorder, this.mPaint);
        }
    }

}
